// 线程池的实现 --  本质就是生产者和消费者模型

// 防止头文件被重复包含
#pragma once

// 线程池 , 所以要有很多线程 , 所以这里需要用封装好的线程
#include "Thread.hpp"
#include "Cond.hpp"
#include "Log.hpp"
#include "Mutex.hpp"
#include <iostream>
#include <vector>
#include <queue>

//////////// 实现单列模式 -- 就是只能有一个线程池对象
// 所以 , 要禁用赋值和拷贝
// 只允许这个类自己创建一次 , 走一次构造
// 所以 , 解决办法就是将构造私有 , 定义时就构造自己构造

// 线程池要做到 :
//  1. 启动线程池 , 帮我创建好线程 , 生产者生产 , 消费者消费
//  2. 终止线程池 , 线程池的线程处理完任务 , 线程必须终止
//  3. 等待所有线程

// 所以 , 线程池的核心就是 : 让多个线程获取任务和处理任务
// 任务在哪 ?? 任务队列
// 所以 , 在线程池创建时 , 我们要创建线程的同时要让线程获取并处理任务

// 一个线程池一旦启动 , 证明要创建很多线程 , 要让线程生产 , 消费任务
// 所以 , 线程池就是一个生产者和消费者模型 , 所以要实现同步 , 互斥
// 同步用条件变量实现

namespace ThreadPoolModel
{
    // 1. 展开线程的命名空间
    using namespace ThreadModel;
    // 2. 展开条件变量的命名空间
    using namespace CondModel;
    // 3. 展开日志的命名空间
    using namespace LogModel;
    // 4. 展开锁的命名空间
    using namespace MutexModel;
    // 5. 默认线程池创建多少个线程
    const int default_thread_number = 5;

    template <class T>
    class ThreadPool
    {
    private:
        // 唤醒所有线程
        void WakeUpAllThread()
        {
            {
                // 因为访问了临界资源  _is_sleep_num , 所以加锁
                LockGuard lockguard(_mutex);
                // 如果有正在休眠的线程 , 就都唤醒 , 即 : _is_sleep_num > 0
                if (_is_sleep_num)
                {
                    // 唤醒条件变量下等待的所有线程
                    _cond.BroadCast();
                    LOG(LogLevel::INFO) << " wake up All sleeping thread success ! ";
                }
            }
        }

        // 唤醒一个线程
        void WakeUpOneThread()
        {
            // 唤醒条件变量下等待的一个线程
            _cond.Signal();
            LOG(LogLevel::INFO) << " wake up One sleeping thread success ! ";
        }

        //// ***************  实现单例模式 ***************

        // 构造函数 , 因为是生产者和消费者模型 , 所以要消费者怎么执行任务 ??
        // 线程池创建出来就是让线程获取并处理任务的
        // 所以 , 这里直接构造时用 lambda 传入每一个线程(消费者)要执行的任务
        ThreadPool()
            : _thread_num(default_thread_number), _is_running(false), _is_sleep_num(0)
        {
            // 创建多个线程 , 就要每个线程都要执行处理任务和获取任务
            for (int i = 0; i < _thread_num; ++i)
            {
                // 直接插入 lambda , 每个线程直接执行  HandlerTask() 方法
                // 会自动转换为 Thread 对象 , 因为 Thread 对象构造时也是 void() 类型
                // 这个lambda 就是这个
                //  HandlerTask() 里面是要获取和处理任务 , 所以要访问ThreadPool的成员和函数
                // 所以 , 捕捉一下 ThreadPool
                _threads.emplace_back([this]()
                                      { HandlerTask(); });
            }
        }

        // 启动线程池 , 这里就是真正的创建每一个线程
        void Start()
        {
            if (_is_running)
                return; // 线程池已经运行了 , 退出 , 去执行任务等 ...

            // 线程池启动了
            _is_running = true;

            // 线程池没有运行 , 还没有创建线程
            // _threads 中已经有了 Thread 对象了
            for (auto &thread : _threads)
            {
                thread.Start(); // 启动线程 , 真正创建线程
                LOG(LogLevel::DEBUG) << "create a new thread [ " << thread.GetThreadName() << " ] ";
            }
        }

    public:
        //// ***************  实现单例模式 ***************
        // 禁用拷贝构造和赋值重载 , 因为只能有一个对象
         ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
        // 所以 , 单例模式下 : 实现懒汉模式 , 定义成指针 , 啥时候用啥时候申请
        // 但是因为单例了 ,所以只能自己创建对象

        // 这个就是让外部调用的 , 因为实现了单例模式 , 外部不能创建对象
        // 所以 , 要调用就要提供 Get 方法来实例对象
        // 静态函数才能访问静态类成员
        static ThreadPool<T> *GetInstance()
        {
            // 这里是线程安全的 ,通过加双层循环保证
            // 一个为空了 , 证明没有对象 , 在创建
            // 已经不为空了 , 就没有必要在竞争锁走 Get 函数了
            if (inc == nullptr)
            {

                {
                    // 但是每个线程都要走这个 , 所以要静态加锁
                    LockGuard lockguard(_lock);
                    // 只允许创建一次
                    // 指针为空 , 我就进来创建 , 即 : new 对象
                    if (inc == nullptr)
                    {
                        // 创建对象
                        inc = new ThreadPool<T>();
                        // 启动线程池
                        inc->Start();
                    }
                }
                // 否则 , 我第一次已经创建好了 , 所以 , 后面不能创建对象了
                // 你后面的用我第一次创建好的对象 , 我会给你返回对象你用
            }
            return inc;
        }

        // 写一个获取并处理任务的方法
        // 所以要在阻塞队列中获取 , 为空时等待 , 不为空唤醒
        // 所以 , 要有条件变量
        // 其实就是 : 消费者消费的接口 !!!!!!!!!!!!
        void HandlerTask()
        {
            char name[128] = {0};
            // 获取一个来的线程的名字
            pthread_getname_np(pthread_self(), name, sizeof(name));

            //  一直处理获取任务 , 但是还要停止呀
            while (true)
            {
                T t;
                {
                    // 所有线程都要进这里获取处理任务 , 所以要加锁
                    LockGuard lockguard(_mutex);

                    // 1 . 队列为空 ,等待 , 但是线程池都停止了,就不要在获取了
                    while (_task_queue.empty() && _is_running)
                    {
                        _is_sleep_num++;
                        _cond.Wait(_mutex);
                        _is_sleep_num--;
                    }

                    // 2. 如果线程都停止了 , 要退出了 , 队列也没任务 , 线程池也停止了 , 我也要退
                    if (_task_queue.empty() && !_is_running)
                    {
                        LOG(LogLevel::INFO) << name << " , I Exit : task_queue is nullptr && thread_pool exit !";
                        break;
                    }

                    // 这里队列一定不为空 , 所以一定有任务 , 获取任务
                    t = _task_queue.front(); // 获取任务
                    _task_queue.pop();
                }

                // 到这里已经获取了一个任务了 , 所以现在要执行任务
                // 这里用函数回调 , 但是线程最终的调用是 pthread_create 中的 Routinue函数执行
                // 所以 , 就最终会调用 Routinue 函数的 Self->_task() 执行真正的任务

                //****** 任务不加锁 , 处理任务本身就是让自己处理 , 不加锁也可以让线程在生产时也能处理 */
                t(); // 执行任务
            }
        }

        // 生产者生产
        bool Enqueue(const T &in)
        {
            // 线程池启动了 , 我就可以生产了
            if (_is_running)
            {
                _task_queue.push(in); // 任务入队列
                // 如果所有线程都在休眠 , 我就唤醒一个让它消费
                if (_threads.size() == _is_sleep_num)
                    WakeUpOneThread();
                return true;
            }

            return false;
        }

        // 线程池停止
        // 线程池停止了 , 证明线程也要停止 , 但是有的线程可能还在处理任务呀
        // 所以 , 有任务处理时就不能停止
        // 那怎么做 ?? 所以 , 在线程池停止内部 , 我先唤醒所有线程 , 让他们把任务处理完 , 我再停止
        void Stop()
        {
            if (!_is_running)
                return; // 线程池没运行 , 不能停止

            // 线程池停止了
            _is_running = false;

            // 唤醒所有线程 , 把所有任务处理完了 , 我再停止
            WakeUpAllThread();
        }

        // 线程池等待所有线程
        void Join()
        {
            for (auto &thread : _threads)
            {
                thread.Join();
            }
        }

        ~ThreadPool() {}

    private:
        std::vector<Thread> _threads; // 管理多个线程
        int _thread_num;              // 线程池中线程的个数
        bool _is_running;             // 线程是否运行中
        int _is_sleep_num;            // 正在休眠的线程的个数

        std::queue<T> _task_queue; // 任务队列 , 生产和消费都在这里
        Cond _cond;                // 条件变量
        Mutex _mutex;              // 锁

        // 单例指针
        // 静态生命周期全局有效 ,所以定义静态的
        static ThreadPool<T> *inc; // 要是静态的 , 因为里面的方式是静态的 , 因为静态的才没有 this 指针
        // 提供Get函数锁 , 因为所有线程都要访问 , 函数是静态的 , 所以锁也是静态
        static Mutex _lock;
    };

    // 静态成员初始化必须在类外 , 指定类域初始化
    template <typename T>
    ThreadPool<T> *ThreadPool<T>::inc = nullptr;

    template <class T>
    Mutex ThreadPool<T>::_lock;

}